package profiler;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * copy from arthas
 *
 * @author hengyunabc 2019-10-31
 */
public class ProfilerExecutor {
    private static final Logger logger = LoggerFactory.getLogger(ProfilerExecutor.class);
    private static ProfilerExecutor instance;
    private String action;
    private String actionArg;

    private String event = "cpu";

    private String file;
    /**
     * output file format, default value is svg.
     */
    private String format = "svg";

    /**
     * sampling interval in ns (default: 10'000'000, i.e. 10 ms)
     */
    private Long interval = 10000000L;

    /**
     * profile different threads separately
     */
    private boolean threads;

    /**
     * include only kernel-mode events
     */
    private boolean allkernel;

    /**
     * include only user-mode events
     */
    private boolean alluser;

    private static String libPath;
    private static AsyncProfiler profiler = null;

    public static void initMac() {
        libPath = "/Users/jonny/code/self-workspace/install-ren-exercise/agent-module/src/main/resources/async-profiler/libasyncProfiler-mac-x64.so";
    }

    public static void init(String sandboxHome) {
        libPath = sandboxHome + "/libasyncProfiler-linux-x64.so";
    }

    public void setEvent(String event) {
        this.event = event;
    }

    public void setThreads(boolean threads) {
        this.threads = threads;
    }

    public void setAllkernel(boolean allkernel) {
        this.allkernel = allkernel;
    }

    public void setAlluser(boolean alluser) {
        this.alluser = alluser;
    }

    public void setFile(String file) {
        this.file = file;
    }

    public void setInterval(Long interval) {
        this.interval = interval;
    }

    public static ProfilerExecutor getInstance() {
        if (instance == null) {
            instance = new ProfilerExecutor();
        }
        return instance;
    }

    private AsyncProfiler profilerInstance() {
        if (profiler != null) {
            return profiler;
        }

        // try to load from special path
        if (ProfilerAction.load.toString().equals(action)) {
            profiler = AsyncProfiler.getInstance(this.actionArg);
        }
        profiler = AsyncProfiler.getInstance(libPath);

        return profiler;
    }

    /**
     * https://github.com/jvm-profiling-tools/async-profiler/blob/v1.6/src/arguments.cpp#L34
     */
    enum ProfilerAction {
        execute, start, stop, resume, list, version, status, load,

        dumpCollapsed, dumpFlat, dumpTraces, getSamples,

        actions
    }

    private String executeArgs(ProfilerAction action) {
        StringBuilder sb = new StringBuilder();

        // start - start profiling
        // resume - start or resume profiling without resetting collected data
        // stop - stop profiling
        sb.append(action).append(',');

        if (this.event != null) {
            sb.append("event=").append(this.event).append(',');
        }
        if (this.file != null) {
            sb.append("file=").append(this.file).append(',');
        }
        if (this.interval != null) {
            sb.append("interval=").append(this.interval).append(',');
        }
        if (this.threads) {
            sb.append("threads").append(',');
        }
        if (this.allkernel) {
            sb.append("allkernel").append(',');
        }
        if (this.alluser) {
            sb.append("alluser").append(',');
        }

        return sb.toString();
    }

    private static String execute(AsyncProfiler asyncProfiler, String arg)
            throws IllegalArgumentException, IOException {
        String result = asyncProfiler.execute(arg);
        if (!result.endsWith("\n")) {
            result += "\n";
        }
        return result;
    }

    public void start() throws IOException {
        AsyncProfiler asyncProfiler = this.profilerInstance();
        String executeArgs = executeArgs(ProfilerAction.start);
        String result = execute(asyncProfiler, executeArgs);
        System.out.println("start.result:" + result);
    }

    public void stop() throws IOException {
        if (this.file == null) {
            this.file = new File("temp",
                    new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date()) + "." + this.format)
                    .getAbsolutePath();
        }
        AsyncProfiler asyncProfiler = this.profilerInstance();
        String executeArgs = executeArgs(ProfilerAction.stop);
        String result = execute(asyncProfiler, executeArgs);
        System.out.println("stop.result:" + result);

    }


}
